package net.oschina.arvin.sqlbuilder.definition;

import net.oschina.arvin.sqlbuilder.annotations.*;
import net.oschina.arvin.sqlbuilder.enums.ColumnMapStrategy;
import net.oschina.arvin.sqlbuilder.utils.AssertUtil;
import net.oschina.arvin.sqlbuilder.utils.ReflectUtil;
import net.oschina.arvin.sqlbuilder.utils.Util;

import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

/**
 * @author Arvin
 * @time 2017/11/16 12:50
 */
public class ColumnDefinition implements Comparable<ColumnDefinition> {

    /** 属性类型 */
    private final Class<?> fieldType;

    /** 属性 */
    private final Field field;

    /** Java 属性名称 */
    private final String fieldName;

    /** 列名称 */
    private final String wrapColumnName;

    /** 列名称，未包装 */
    private final String unWrapColumnName;

    /** 不允许为空 */
    private final boolean notNull;

    /** 不允许为空字符串 */
    private final boolean notBlank;

    /** 最小值限制 */
    private final Long min;

    /** 最大值限制 */
    private final Long max;

    /**
     * 添加的时候使用当前时间填充
     */
    private final boolean useCurrentTimeWhenAdd;

    /**
     * 更新的时候使用当前时间填充
     */
    private final boolean useCurrentTimeWhenUpdate;

    /** 插入的时候是否忽略该属性 */
    private final boolean insertIgnore;

    /** 更新的时候是否忽略该属性 */
    private final boolean updateIgnore;

    /** 更新的时候默认是不包含null值的 */
    private final boolean updateIncludeNull;

    /** 插入的时候默认是不包含null值的 */
    private final boolean insertIncludeNull;

    /** 默认值 */
    private final Object defaultValue;

    /** 主键注解 */
    private final Column columnAnnotation;

    /** 列生成策略 */
    private final ColumnMapStrategy columnMapStrategy;


    /** 主键注解 */
    private final PrimaryKey primaryKeyAnnotation;

    /** 是否自增 */
    private final boolean autoIncrement;

    /** 是否使用 UUID */
    private final boolean useUuid;

    /** 是否可以自增 */
    private final boolean canAutoIncrement;

    /** 是否可以使用 UUID */
    private final boolean canUseUuid;

    /** 主键注解 */
    private final UniqueKey uniqueKeyAnnotation;

    public ColumnDefinition(Field field, ColumnMapStrategy parentColumnMapStrategy) {
        AssertUtil.assertNotNull(field, "Field不能为空");

        this.field = field;
        this.fieldName = this.field.getName();
        this.fieldType = this.field.getType();

        this.columnAnnotation = ReflectUtil.getFieldAnnotation(this.field, Column.class);
        this.columnMapStrategy = parseColumnMapStrategyByColumnAnnotation(columnAnnotation, parentColumnMapStrategy);
        String columnName;
        String modelFieldName = this.fieldName;
        if (null != columnAnnotation && Util.isNotBlank(columnAnnotation.name())) {
            columnName = columnAnnotation.name();
        } else {
            columnName = columnMapStrategy.getMapColumn(modelFieldName);
        }

        NotNull notNullAnnotation = ReflectUtil.getFieldAnnotation(this.field, NotNull.class);
        this.notNull = notNullAnnotation != null || Util.isPrimitiveType(this.fieldType);

        NotBlank notBlankAnnotation = ReflectUtil.getFieldAnnotation(this.field, NotBlank.class);
        this.notBlank = notBlankAnnotation != null && Util.isStringType(this.fieldType);

        Min minAnnotation = ReflectUtil.getFieldAnnotation(this.field, Min.class);
        this.min = null != minAnnotation ? minAnnotation.value() : null;

        Max maxAnnotation = ReflectUtil.getFieldAnnotation(this.field, Max.class);
        this.max = null != maxAnnotation ? maxAnnotation.value() : null;

        UpdateIncludeNull updateIncludeNullAnnotation = ReflectUtil.getFieldAnnotation(this.field, UpdateIncludeNull.class);
        this.updateIncludeNull = updateIncludeNullAnnotation != null;

        InsertIncludeNull insertIncludeNullAnnotation = ReflectUtil.getFieldAnnotation(this.field, InsertIncludeNull.class);
        this.insertIncludeNull = insertIncludeNullAnnotation != null;

        this.useCurrentTimeWhenAdd = this.columnAnnotation != null && this.columnAnnotation.useCurrentTimeWhenAdd() && Util.isTimeType(this.fieldType);
        this.useCurrentTimeWhenUpdate = this.columnAnnotation != null && this.columnAnnotation.useCurrentTimeWhenUpdate() && Util.isTimeType(this.fieldType);

        this.insertIgnore = this.columnAnnotation != null && this.columnAnnotation.insertIgnore();
        this.updateIgnore = this.columnAnnotation != null && this.columnAnnotation.updateIgnore();

        this.defaultValue = calculateDefaultValue(this.field);

        this.unWrapColumnName = Util.unWrapColumnName(columnName);
        this.wrapColumnName = Util.wrapColumnName(columnName);

        this.canAutoIncrement = isFieldTypeCanAutoIncrement(this.fieldType);
        this.canUseUuid = isFieldTypeCanUseUuid(this.fieldType);

        this.primaryKeyAnnotation = ReflectUtil.getFieldAnnotation(this.field, PrimaryKey.class);
        if (null != this.primaryKeyAnnotation) {
            if (this.canAutoIncrement) {
                this.autoIncrement = this.primaryKeyAnnotation.autoIncrement();
                this.useUuid = false;
            } else if (canUseUuid) {
                this.useUuid = this.primaryKeyAnnotation.useUuid();
                this.autoIncrement = false;
            } else {
                this.autoIncrement = false;
                this.useUuid = false;
            }
        } else {
            this.autoIncrement = false;
            this.useUuid = false;
        }

        this.uniqueKeyAnnotation = ReflectUtil.getFieldAnnotation(this.field, UniqueKey.class);

    }

    /**
     * 计算属性的默认值
     *
     * @param field 属性
     */
    public static Object calculateDefaultValue(Field field) {

        AssertUtil.assertNotNull(field, "要计算默认值的java属性不能为null");

        Class<?> fieldType = field.getType();

        if (Util.isStringType(fieldType)) {
            DefaultString defaultStringAnnotation = ReflectUtil.getFieldAnnotation(field, DefaultString.class);
            if (null != defaultStringAnnotation) {
                return defaultStringAnnotation.value();
            }
            return null;
        }

        if (Util.isIntType(fieldType)) {
            DefaultInt defaultIntAnnotation = ReflectUtil.getFieldAnnotation(field, DefaultInt.class);
            if (null != defaultIntAnnotation) {
                return defaultIntAnnotation.value();
            }
            return 0;
        }

        if (Util.isLongType(fieldType)) {
            DefaultLong defaultLongAnnotation = ReflectUtil.getFieldAnnotation(field, DefaultLong.class);
            if (null != defaultLongAnnotation) {
                return defaultLongAnnotation.value();
            }
            return 0L;
        }

        if (Util.isBooleanType(fieldType)) {
            DefaultBoolean defaultBooleanAnnotation = ReflectUtil.getFieldAnnotation(field, DefaultBoolean.class);
            if (null != defaultBooleanAnnotation) {
                return defaultBooleanAnnotation.value();
            }
            return null;
        }

        return Util.getTypeDefaultValue(fieldType);
    }

    public static ColumnMapStrategy parseColumnMapStrategyByColumnAnnotation(Column columnAnnotation, ColumnMapStrategy defaultColumnMapStrategy) {
        defaultColumnMapStrategy = null == defaultColumnMapStrategy ? ColumnMapStrategy.UNDERLINE : defaultColumnMapStrategy;
        if (null == columnAnnotation) {
            return defaultColumnMapStrategy;
        }
        if (null != columnAnnotation.mapStrategy()) {
            return columnAnnotation.mapStrategy();
        }
        return defaultColumnMapStrategy;
    }

    /**
     * 检查给定的值是否接受， 如果不接受就会抛出异常
     *
     * @param value 要检查的值
     */
    public void checkValue(Object value) {

        String prefixMsg = "字段[" + this.fieldName + "], 对应列[" + this.unWrapColumnName + "] ";

        AssertUtil.assertFalse(notNull && null == value, prefixMsg + "不能为NULL!");

        if (null != value) {

            AssertUtil.assertFalse(this.fieldType == value.getClass(), prefixMsg + "类型不匹配");

            AssertUtil.assertFalse(notBlank && Util.isStringType(value.getClass()) && Util.isBlank((String) value),
                    prefixMsg + "不能为空字符串!");

            AssertUtil.assertFalse(min != null && Util.isNumberType(value.getClass()) && Util.toNumber(value).longValue() < min,
                    prefixMsg + " 不能小于[" + min + "]");

            AssertUtil.assertFalse(max != null && Util.isNumberType(value.getClass()) && Util.toNumber(value).longValue() > max,
                    prefixMsg + " 不能大于[" + max + "]");

        }

    }

    /**
     * 是否允许使用 UUID 作为主键
     *
     * @param fieldType 字段类型
     */
    @SuppressWarnings({"unchecked"})
    public static boolean isFieldTypeCanUseUuid(Class<?> fieldType) {
        Set<Class<?>> canUseUuidTypes = new HashSet<Class<?>>(Arrays.asList(
                String.class, CharSequence.class
        ));
        return null != fieldType && canUseUuidTypes.contains(fieldType);
    }

    /**
     * 是否允许自增， int Integer long Long 才能自增
     *
     * @param fieldType 字段类型
     */
    @SuppressWarnings({"unchecked"})
    public static boolean isFieldTypeCanAutoIncrement(Class<?> fieldType) {
        Set<Class<?>> canAutoIncrementTypes = new HashSet<Class<?>>(Arrays.asList(
                int.class, Integer.class, long.class, Long.class
        ));
        return null != fieldType && canAutoIncrementTypes.contains(fieldType);
    }

    public Class<?> getFieldType() {
        return fieldType;
    }

    public Field getField() {
        return field;
    }

    public String getFieldName() {
        return fieldName;
    }

    public String getWrapColumnName() {
        return wrapColumnName;
    }

    public String getUnWrapColumnName() {
        return unWrapColumnName;
    }

    public boolean isNotNull() {
        return notNull;
    }

    public boolean isNotBlank() {
        return notBlank;
    }

    public Long getMin() {
        return min;
    }

    public Long getMax() {
        return max;
    }

    public boolean isUpdateIncludeNull() {
        return updateIncludeNull;
    }

    public boolean isInsertIncludeNull() {
        return insertIncludeNull;
    }

    public boolean isUseCurrentTimeWhenAdd() {
        return useCurrentTimeWhenAdd;
    }

    public boolean isUseCurrentTimeWhenUpdate() {
        return useCurrentTimeWhenUpdate;
    }

    public boolean isInsertIgnore() {
        return insertIgnore;
    }

    public boolean isUpdateIgnore() {
        return updateIgnore;
    }

    public Object getDefaultValue() {
        return defaultValue;
    }

    public Column getColumnAnnotation() {
        return columnAnnotation;
    }

    public ColumnMapStrategy getColumnMapStrategy() {
        return columnMapStrategy;
    }

    public PrimaryKey getPrimaryKeyAnnotation() {
        return primaryKeyAnnotation;
    }

    public boolean isAutoIncrement() {
        return autoIncrement;
    }

    public boolean isUseUuid() {
        return useUuid;
    }

    public boolean isCanAutoIncrement() {
        return canAutoIncrement;
    }

    public boolean isCanUseUuid() {
        return canUseUuid;
    }

    public UniqueKey getUniqueKeyAnnotation() {
        return uniqueKeyAnnotation;
    }

    public boolean isPrimaryKey() {
        return null != primaryKeyAnnotation;
    }

    public boolean isUniqueKey() {
        return null != uniqueKeyAnnotation;
    }

    public boolean isNormalKey() {
        return !isUniqueKey() && !isPrimaryKey();
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        ColumnDefinition that = (ColumnDefinition) o;

        return field.equals(that.field);

    }

    @Override
    public int hashCode() {
        return field.hashCode();
    }

    @Override
    public int compareTo(ColumnDefinition that) {

        // 排序的规则： 主键，唯一键， 其他字段
        boolean selfIsPrimaryKey = isPrimaryKey();
        boolean thatIsPrimaryKey = that.isPrimaryKey();

        if (selfIsPrimaryKey && thatIsPrimaryKey) {
            return 0;
        }
        if (selfIsPrimaryKey) {
            return -1;
        }
        if (thatIsPrimaryKey) {
            return 1;
        }

        boolean selfIsUniqueKey = isUniqueKey();
        boolean thatIsUniqueKey = that.isUniqueKey();
        if (selfIsUniqueKey && thatIsUniqueKey) {
            return 0;
        }
        if (selfIsUniqueKey) {
            return -1;
        }
        if (thatIsUniqueKey) {
            return 1;
        }

        return 0;
    }
}
